import matplotlib.pyplot as plt
import seaborn as sns
import plotly.graph_objects as go
import plotly.express as px
import geopandas as gpd
import pandas as pd
import contextily as ctx
from PIL import Image, ImageDraw, ImageFont
import imageio
import numpy as np
from typing import List, Dict, Tuple, Any, Optional, Union
import os
import logging
from config import MAP_CONFIG

logger = logging.getLogger(__name__)

class ImageRenderingTools:
    """图片渲染工具类 - 专注于PNG和GIF格式的图表生成"""
    
    SUPPORTED_FORMATS = ['png', 'gif']  # 只支持PNG和GIF格式
    
    def __init__(self):
        self.output_dir = MAP_CONFIG['output_dir']
        self.temp_dir = MAP_CONFIG['temp_dir']
        self.default_center = MAP_CONFIG['default_center']
        
        # 确保输出目录存在
        os.makedirs(self.output_dir, exist_ok=True)
        os.makedirs(self.temp_dir, exist_ok=True)
        
        # 设置matplotlib全局参数
        self._setup_matplotlib()
    
    def _setup_matplotlib(self):
        """设置matplotlib全局参数"""
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
        plt.rcParams['axes.unicode_minus'] = False
        plt.rcParams['figure.facecolor'] = 'white'
        plt.rcParams['axes.facecolor'] = 'white'
        plt.rcParams['savefig.facecolor'] = 'white'
        plt.rcParams['savefig.edgecolor'] = 'none'
        plt.rcParams['savefig.dpi'] = 300
        plt.rcParams['savefig.bbox'] = 'tight'
    
    def create_statistical_chart(self, data: Optional[Dict] = None, chart_type: str = 'bar',
                                title: str = "统计图表", save_path: Optional[str] = None) -> str:
        """创建统计图表 - 支持柱状图、线图、饼图、直方图等"""
        try:
            fig, ax = plt.subplots(figsize=(12, 8))
            
            if data is None:
                # 创建示例统计数据
                np.random.seed(42)
                if chart_type == 'bar':
                    categories = ['产品A', '产品B', '产品C', '产品D', '产品E']
                    values = np.random.randint(50, 200, 5)
                    bars = ax.bar(categories, values, color=['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57'], alpha=0.8)
                    ax.set_xlabel('产品类型', fontsize=12)
                    ax.set_ylabel('销售量', fontsize=12)
                    # 添加数值标签
                    for bar in bars:
                        height = bar.get_height()
                        ax.text(bar.get_x() + bar.get_width()/2., height + 2,
                               f'{int(height)}', ha='center', va='bottom', fontweight='bold')
                               
                elif chart_type == 'line':
                    months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
                    values = 100 + np.cumsum(np.random.randn(12) * 10)
                    ax.plot(months, values, 'o-', linewidth=3, markersize=8, color='#FF6B6B')
                    ax.fill_between(months, values, alpha=0.3, color='#FF6B6B')
                    ax.set_xlabel('月份', fontsize=12)
                    ax.set_ylabel('销售额 (万元)', fontsize=12)
                    plt.xticks(rotation=45)
                    
                elif chart_type == 'pie':
                    sizes = [30, 25, 20, 15, 10]
                    labels = ['市场A', '市场B', '市场C', '市场D', '其他']
                    colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57']
                    wedges, texts, autotexts = ax.pie(sizes, labels=labels, colors=colors, 
                                                     autopct='%1.1f%%', startangle=90, 
                                                     explode=(0.1, 0, 0, 0, 0))
                    for autotext in autotexts:
                        autotext.set_color('white')
                        autotext.set_fontweight('bold')
                        
                elif chart_type == 'histogram':
                    data_values = np.random.normal(100, 15, 1000)
                    ax.hist(data_values, bins=30, color='#45B7D1', alpha=0.7, edgecolor='black')
                    ax.set_xlabel('数值', fontsize=12)
                    ax.set_ylabel('频次', fontsize=12)
                    ax.axvline(np.mean(data_values), color='red', linestyle='--', linewidth=2, label=f'均值: {np.mean(data_values):.1f}')
                    ax.legend()
            else:
                # 使用提供的数据
                if chart_type == 'bar' and 'x' in data and 'y' in data:
                    bars = ax.bar(data['x'], data['y'], alpha=0.8)
                    # 添加数值标签
                    for bar in bars:
                        height = bar.get_height()
                        ax.text(bar.get_x() + bar.get_width()/2., height + max(data['y'])*0.01,
                               f'{height}', ha='center', va='bottom', fontweight='bold')
                elif chart_type == 'line' and 'x' in data and 'y' in data:
                    ax.plot(data['x'], data['y'], 'o-', linewidth=3, markersize=8)
                elif chart_type == 'pie' and 'values' in data and 'labels' in data:
                    ax.pie(data['values'], labels=data['labels'], autopct='%1.1f%%', startangle=90)
                
            ax.set_title(title, fontsize=16, fontweight='bold', pad=20)
            ax.grid(True, alpha=0.3, linestyle='--')
            
            # 保存PNG图片
            if save_path is None:
                timestamp = int(np.random.random() * 1000000)
                save_path = os.path.join(self.output_dir, f"statistical_{chart_type}_{timestamp}.png")
            
            plt.tight_layout()
            plt.savefig(save_path)
            plt.close()
            
            logger.info(f"统计图表已保存: {save_path}")
            return save_path
            
        except Exception as e:
            logger.error(f"创建统计图表失败: {e}")
            raise
    
    def create_scatter_plot(self, data: Optional[Dict] = None, title: str = "散点图", 
                           save_path: Optional[str] = None, with_regression: bool = True) -> str:
        """创建高级散点图 - 支持回归线、颜色编码、大小编码等"""
        try:
            fig, ax = plt.subplots(figsize=(12, 8))
            
            if data is None:
                # 创建示例散点数据
                np.random.seed(42)
                n_points = 200
                x = np.random.normal(0, 2, n_points)
                y = 2 * x + np.random.normal(0, 1, n_points)
                colors = np.random.rand(n_points) * 100  # 颜色编码
                sizes = np.random.randint(30, 200, n_points)  # 大小编码
                
                # 创建散点图
                scatter = ax.scatter(x, y, c=colors, s=sizes, alpha=0.6, 
                                   cmap='viridis', edgecolors='black', linewidth=0.5)
                
                # 添加颜色条
                cbar = plt.colorbar(scatter, ax=ax)
                cbar.set_label('数据值', fontsize=12)
                
                # 添加回归线
                if with_regression:
                    z = np.polyfit(x, y, 1)
                    p = np.poly1d(z)
                    ax.plot(x, p(x), "r--", linewidth=2, alpha=0.8, label=f'回归线: y={z[0]:.2f}x+{z[1]:.2f}')
                    ax.legend()
                    
                ax.set_xlabel('X变量', fontsize=12)
                ax.set_ylabel('Y变量', fontsize=12)
                
            else:
                # 使用提供的数据
                if 'x' in data and 'y' in data:
                    scatter_params = {'alpha': 0.6, 'edgecolors': 'black', 'linewidth': 0.5}
                    
                    if 'colors' in data:
                        scatter_params['c'] = data['colors']
                        scatter_params['cmap'] = 'viridis'
                    if 'sizes' in data:
                        scatter_params['s'] = data['sizes']
                    else:
                        scatter_params['s'] = 50
                        
                    scatter = ax.scatter(data['x'], data['y'], **scatter_params)
                    
                    # 添加颜色条（如果有颜色编码）
                    if 'colors' in data:
                        cbar = plt.colorbar(scatter, ax=ax)
                        cbar.set_label('数据值', fontsize=12)
                    
                    # 添加回归线
                    if with_regression and len(data['x']) > 1:
                        z = np.polyfit(data['x'], data['y'], 1)
                        p = np.poly1d(z)
                        x_range = np.linspace(min(data['x']), max(data['x']), 100)
                        ax.plot(x_range, p(x_range), "r--", linewidth=2, alpha=0.8, 
                               label=f'回归线: y={z[0]:.2f}x+{z[1]:.2f}')
                        ax.legend()
            
            ax.set_title(title, fontsize=16, fontweight='bold', pad=20)
            ax.grid(True, alpha=0.3, linestyle='--')
            
            # 保存PNG图片
            if save_path is None:
                timestamp = int(np.random.random() * 1000000)
                save_path = os.path.join(self.output_dir, f"scatter_plot_{timestamp}.png")
            
            plt.tight_layout()
            plt.savefig(save_path)
            plt.close()
            
            logger.info(f"散点图已保存: {save_path}")
            return save_path
            
        except Exception as e:
            logger.error(f"创建散点图失败: {e}")
            raise
    
    def create_geo_chart(self, gdf: Optional[gpd.GeoDataFrame] = None, 
                        title: str = "地理数据图", save_path: Optional[str] = None,
                        point_size: int = 50, colormap: str = 'viridis') -> str:
        """创建地理数据图表 - 支持点、线、面数据的可视化"""
        try:
            fig, ax = plt.subplots(figsize=(14, 10))
            
            if gdf is not None and not gdf.empty:
                # 使用实际地理数据
                if gdf.geometry.geom_type.iloc[0] == 'Point':
                    gdf.plot(ax=ax, color='red', alpha=0.7, markersize=point_size)
                elif gdf.geometry.geom_type.iloc[0] == 'LineString':
                    gdf.plot(ax=ax, color='blue', alpha=0.7, linewidth=2)
                else:  # Polygon
                    gdf.plot(ax=ax, color='green', alpha=0.7, edgecolor='black', linewidth=0.5)
                    
                ax.set_xlabel('经度 (°)', fontsize=12)
                ax.set_ylabel('纬度 (°)', fontsize=12)
            else:
                # 创建示例地理数据
                np.random.seed(42)
                
                # 模拟中国主要城市的地理数据
                cities_data = {
                    '北京': (116.4074, 39.9042, 85),
                    '上海': (121.4737, 31.2304, 90),
                    '广州': (113.2644, 23.1291, 75),
                    '深圳': (114.0579, 22.5431, 80),
                    '杭州': (120.1551, 30.2741, 70),
                    '南京': (118.7915, 32.0515, 65),
                    '武汉': (114.3054, 30.5931, 72),
                    '成都': (104.0665, 30.5723, 68),
                    '西安': (108.9398, 34.3416, 63),
                    '重庆': (106.5516, 29.5630, 67)
                }
                
                # 提取坐标和数值
                lons, lats, values, city_names = [], [], [], []
                for city, (lon, lat, value) in cities_data.items():
                    lons.append(lon)
                    lats.append(lat)
                    values.append(value)
                    city_names.append(city)
                
                # 添加一些随机噪声点
                n_extra = 50
                extra_lons = np.random.uniform(105, 125, n_extra)
                extra_lats = np.random.uniform(25, 42, n_extra)
                extra_values = np.random.uniform(30, 95, n_extra)
                
                all_lons = np.concatenate([lons, extra_lons])
                all_lats = np.concatenate([lats, extra_lats])
                all_values = np.concatenate([values, extra_values])
                
                # 创建散点图
                scatter = ax.scatter(all_lons, all_lats, c=all_values, s=all_values*3, 
                                   alpha=0.7, cmap=colormap, edgecolors='black', linewidth=0.5)
                
                # 标注主要城市
                for i, city in enumerate(city_names):
                    ax.annotate(city, (lons[i], lats[i]), xytext=(5, 5), 
                               textcoords='offset points', fontsize=10, fontweight='bold',
                               bbox=dict(boxstyle='round,pad=0.3', facecolor='white', alpha=0.8))
                
                # 添加颜色条
                cbar = plt.colorbar(scatter, ax=ax)
                cbar.set_label('数据值', fontsize=12)
                
                ax.set_xlabel('经度 (°)', fontsize=12)
                ax.set_ylabel('纬度 (°)', fontsize=12)
                ax.set_xlim(100, 130)
                ax.set_ylim(20, 45)
            
            ax.set_title(title, fontsize=16, fontweight='bold', pad=20)
            ax.grid(True, alpha=0.3, linestyle='--')
            
            # 保存PNG图片
            if save_path is None:
                timestamp = int(np.random.random() * 1000000)
                save_path = os.path.join(self.output_dir, f"geo_chart_{timestamp}.png")
            
            plt.tight_layout()
            plt.savefig(save_path)
            plt.close()
            
            logger.info(f"地理数据图表已保存: {save_path}")
            return save_path
            
        except Exception as e:
            logger.error(f"创建地理数据图表失败: {e}")
            raise
    
    def create_animation_gif(self, data_list: Optional[List[Dict]] = None, 
                           title_list: Optional[List[str]] = None,
                           duration: float = 1.0, save_path: Optional[str] = None,
                           animation_type: str = 'bar') -> str:
        """创建动画GIF - 支持多种动画类型"""
        try:
            if data_list is None:
                # 创建示例动画数据
                data_list = []
                title_list = []
                
                if animation_type == 'bar':
                    # 柱状图动画
                    categories = ['产品A', '产品B', '产品C', '产品D', '产品E']
                    for i in range(12):
                        np.random.seed(i * 5)
                        values = 50 + 30 * np.sin(np.arange(5) + i * 0.5) + np.random.normal(0, 5, 5)
                        data = {'x': categories, 'y': np.maximum(values, 10)}  # 确保值不为负
                        data_list.append(data)
                        title_list.append(f"销售数据动态变化 - 第{i+1}个月")
                        
                elif animation_type == 'scatter':
                    # 散点图动画
                    for i in range(15):
                        np.random.seed(i * 3)
                        n_points = 100
                        angle = i * 0.3
                        x = np.random.normal(0, 1, n_points)
                        y = np.cos(angle) * x + np.sin(angle) * np.random.normal(0, 0.5, n_points)
                        colors = np.random.rand(n_points) * 100
                        data = {'x': x, 'y': y, 'colors': colors}
                        data_list.append(data)
                        title_list.append(f"数据关系演变 - 阶段{i+1}")
                        
                elif animation_type == 'geo':
                    # 地理数据动画
                    base_cities = {
                        '北京': (116.4074, 39.9042),
                        '上海': (121.4737, 31.2304),
                        '广州': (113.2644, 23.1291),
                        '深圳': (114.0579, 22.5431),
                        '杭州': (120.1551, 30.2741)
                    }
                    
                    for i in range(10):
                        lons, lats, values = [], [], []
                        for city, (lon, lat) in base_cities.items():
                            lons.append(lon)
                            lats.append(lat)
                            # 模拟时间序列数据变化
                            values.append(50 + 30 * np.sin(i * 0.8) + np.random.normal(0, 5))
                        
                        data = {'lons': lons, 'lats': lats, 'values': values}
                        data_list.append(data)
                        title_list.append(f"地理数据时序变化 - 时间点{i+1}")
            
            # 创建临时图片文件列表
            temp_images = []
            
            for i, data in enumerate(data_list):
                fig, ax = plt.subplots(figsize=(12, 8))
                
                if animation_type == 'bar' and 'x' in data and 'y' in data:
                    bars = ax.bar(data['x'], data['y'], color=['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57'], 
                                 alpha=0.8)
                    ax.set_ylim(0, max([max(d['y']) for d in data_list]) * 1.1)  # 固定Y轴范围
                    
                    # 添加数值标签
                    for bar in bars:
                        height = bar.get_height()
                        ax.text(bar.get_x() + bar.get_width()/2., height + 1,
                               f'{int(height)}', ha='center', va='bottom', fontweight='bold')
                    
                elif animation_type == 'scatter' and 'x' in data and 'y' in data:
                    scatter = ax.scatter(data['x'], data['y'], c=data.get('colors', 'blue'), 
                                       s=60, alpha=0.6, cmap='viridis')
                    # 固定坐标轴范围
                    all_x = np.concatenate([d['x'] for d in data_list])
                    all_y = np.concatenate([d['y'] for d in data_list])
                    ax.set_xlim(min(all_x) - 0.5, max(all_x) + 0.5)
                    ax.set_ylim(min(all_y) - 0.5, max(all_y) + 0.5)
                    
                elif animation_type == 'geo' and 'lons' in data:
                    scatter = ax.scatter(data['lons'], data['lats'], c=data['values'], 
                                       s=np.array(data['values']) * 4, alpha=0.7, 
                                       cmap='viridis', edgecolors='black', linewidth=0.5)
                    ax.set_xlim(110, 125)
                    ax.set_ylim(22, 42)
                    ax.set_xlabel('经度 (°)', fontsize=12)
                    ax.set_ylabel('纬度 (°)', fontsize=12)
                
                title = title_list[i] if title_list and i < len(title_list) else f"动画图表 - 第{i+1}帧"
                ax.set_title(title, fontsize=14, fontweight='bold', pad=15)
                ax.grid(True, alpha=0.3, linestyle='--')
                
                # 保存临时图片
                temp_path = os.path.join(self.temp_dir, f"temp_frame_{i:03d}.png")
                plt.tight_layout()
                plt.savefig(temp_path)
                plt.close()
                
                temp_images.append(temp_path)
            
            # 创建GIF
            if save_path is None:
                timestamp = int(np.random.random() * 1000000)
                save_path = os.path.join(self.output_dir, f"animation_{animation_type}_{timestamp}.gif")
            
            # 读取所有临时图片并创建GIF
            images = []
            for temp_path in temp_images:
                img = Image.open(temp_path)
                images.append(img)
            
            # 保存GIF
            images[0].save(
                save_path,
                save_all=True,
                append_images=images[1:],
                duration=duration * 1000,  # 转换为毫秒
                loop=0,
                optimize=True
            )
            
            # 清理临时文件
            for temp_path in temp_images:
                try:
                    os.remove(temp_path)
                except:
                    pass
            
            logger.info(f"动画GIF已保存: {save_path}")
            return save_path
            
        except Exception as e:
            logger.error(f"创建动画GIF失败: {e}")
            raise

# 保持向后兼容性的别名
MapRenderingTools = ImageRenderingTools 